package com.exchange.service;

import com.exchange.dto.ExchangeCodeDto;
import com.exchange.entity.ExchangeCode;
import com.exchange.entity.Merchant;
import com.exchange.repository.ExchangeCodeRepository;
import com.exchange.repository.MerchantRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
public class ExchangeCodeService {
    
    @Autowired
    private ExchangeCodeRepository exchangeCodeRepository;
    
    @Autowired
    private MerchantRepository merchantRepository;
    
    @Autowired
    private ResourceLimitService resourceLimitService;
    
    @Autowired
    private RedisService redisService;
    
    public Optional<ExchangeCodeDto> findById(Long id) {
        return exchangeCodeRepository.findById(id)
                .map(ExchangeCodeDto::fromEntity);
    }
    
    public List<ExchangeCodeDto> findByMerchantId(Long merchantId) {
        return exchangeCodeRepository.findByMerchantId(merchantId)
                .stream()
                .map(ExchangeCodeDto::fromEntity)
                .collect(Collectors.toList());
    }
    
    public ExchangeCodeDto createExchangeCode(ExchangeCode exchangeCode, Long merchantId) {
        // Check if merchant has reached exchange code limit
        if (!resourceLimitService.isWithinExchangeCodeLimit(merchantId, null)) {
            throw new RuntimeException("Merchant has reached the maximum number of exchange codes");
        }
        
        exchangeCode.setMerchantId(merchantId);
        exchangeCode.setStatus(ExchangeCode.ExchangeCodeStatus.UNUSED);
        exchangeCode.setCreatedAt(java.time.LocalDateTime.now());
        
        // Generate a unique code if not provided
        if (exchangeCode.getCode() == null || exchangeCode.getCode().isEmpty()) {
            exchangeCode.setCode(generateUniqueCode());
        }
        
        ExchangeCode savedCode = exchangeCodeRepository.save(exchangeCode);
        return ExchangeCodeDto.fromEntity(savedCode);
    }
    
    public List<ExchangeCodeDto> generateBatchExchangeCodes(int count, Long merchantId, LocalDate expirationDate) {
        List<ExchangeCodeDto> generatedCodes = new ArrayList<>();
        
        // Check if merchant has enough quota for the requested count
        Long currentCount = exchangeCodeRepository.countByMerchantId(merchantId);
        Optional<Merchant> merchantOpt = merchantRepository.findById(merchantId);
        Integer maxCodes = merchantOpt.map(Merchant::getMaxExchangeCodes).orElse(null);
        
        if (maxCodes != null && currentCount + count > maxCodes) {
            throw new RuntimeException("Merchant would exceed the maximum number of exchange codes with this batch");
        }
        
        for (int i = 0; i < count; i++) {
            ExchangeCode exchangeCode = new ExchangeCode();
            exchangeCode.setMerchantId(merchantId);
            exchangeCode.setStatus(ExchangeCode.ExchangeCodeStatus.UNUSED);
            exchangeCode.setExpirationDate(expirationDate);
            exchangeCode.setCreatedAt(java.time.LocalDateTime.now());
            exchangeCode.setCode(generateUniqueCode());
            
            ExchangeCode savedCode = exchangeCodeRepository.save(exchangeCode);
            generatedCodes.add(ExchangeCodeDto.fromEntity(savedCode));
        }
        
        return generatedCodes;
    }
    
    public ExchangeCodeDto updateExchangeCode(Long id, ExchangeCode exchangeCodeDetails) {
        Optional<ExchangeCode> existingCode = exchangeCodeRepository.findById(id);
        if (existingCode.isPresent()) {
            ExchangeCode code = existingCode.get();
            code.setExpirationDate(exchangeCodeDetails.getExpirationDate());
            code.setStatus(exchangeCodeDetails.getStatus());
            code.setUsedBy(exchangeCodeDetails.getUsedBy());
            
            ExchangeCode updatedCode = exchangeCodeRepository.save(code);
            return ExchangeCodeDto.fromEntity(updatedCode);
        }
        return null;
    }
    
    public boolean deleteExchangeCode(Long id) {
        if (exchangeCodeRepository.existsById(id)) {
            exchangeCodeRepository.deleteById(id);
            return true;
        }
        return false;
    }
    
    public Optional<ExchangeCodeDto> findByCode(String code) {
        // First check if result is in cache
        Object cachedResult = redisService.getCachedExchangeCodeValidation(code);
        if (cachedResult != null) {
            // If cached result indicates the code was not found, return empty
            if (cachedResult instanceof String && "NOT_FOUND".equals(cachedResult)) {
                return Optional.empty();
            }
            // If cached result is an ExchangeCodeDto, return it
            if (cachedResult instanceof ExchangeCodeDto) {
                return Optional.of((ExchangeCodeDto) cachedResult);
            }
        }
        
        Optional<ExchangeCodeDto> result = exchangeCodeRepository.findByCode(code)
                .map(ExchangeCodeDto::fromEntity);
        
        // Cache the result (whether found or not)
        if (result.isPresent()) {
            redisService.cacheExchangeCodeValidation(code, result.get());
        } else {
            redisService.cacheExchangeCodeValidation(code, "NOT_FOUND");
        }
        
        return result;
    }
    
    public Object useExchangeCode(String code, String password) {
        Optional<ExchangeCode> exchangeCodeOpt = exchangeCodeRepository.findByCode(code);
        
        if (exchangeCodeOpt.isPresent()) {
            ExchangeCode exchangeCode = exchangeCodeOpt.get();
            
            // Check if code is valid (not expired and unused)
            if (!exchangeCode.isUsable()) {
                return new ErrorResponse("Exchange code is not valid", "CODE_INVALID");
            }
            
            // Update the code status to used
            exchangeCode.setStatus(ExchangeCode.ExchangeCodeStatus.USED);
            exchangeCode.setUsedAt(java.time.LocalDateTime.now());
            exchangeCodeRepository.save(exchangeCode);
            
            // Clear the cache for this code since its status changed
            redisService.delete("exchange_code:" + code);
            
            // Create and return success response with the products associated with the code
            // In a real implementation, we'd fetch the products associated with this code
            return new SuccessResponse("Exchange code used successfully", "CODE_USED", exchangeCode);
        } else {
            return new ErrorResponse("Exchange code not found", "CODE_NOT_FOUND");
        }
    }
    
    private String generateUniqueCode() {
        // Generate a unique code (in a real implementation, you might want to use a more sophisticated algorithm)
        String code = "CODE" + System.currentTimeMillis() + java.util.UUID.randomUUID().toString().substring(0, 6).toUpperCase();
        // In a real implementation, you'd want to verify the code is unique in the database
        return code;
    }
    
    // Response classes
    public static class SuccessResponse {
        private String message;
        private String status;
        private Object data;
        
        public SuccessResponse(String message, String status, Object data) {
            this.message = message;
            this.status = status;
            this.data = data;
        }
        
        // Getters and setters
        public String getMessage() { return message; }
        public void setMessage(String message) { this.message = message; }
        
        public String getStatus() { return status; }
        public void setStatus(String status) { this.status = status; }
        
        public Object getData() { return data; }
        public void setData(Object data) { this.data = data; }
    }
    
    public static class ErrorResponse {
        private String message;
        private String status;
        
        public ErrorResponse(String message, String status) {
            this.message = message;
            this.status = status;
        }
        
        // Getters and setters
        public String getMessage() { return message; }
        public void setMessage(String message) { this.message = message; }
        
        public String getStatus() { return status; }
        public void setStatus(String status) { this.status = status; }
    }
}